/*
 * PropertyFactory.c
 *
 *  Created on: 09.08.2011
 *      Author: sili
 */

#include "PropertyFactory.h"
#include "AALItem.h"

static log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("at.sili.aalsystem.factories.PropertyFactory"));

static pthread_rwlock_t propertyFactoryRWLock = PTHREAD_RWLOCK_INITIALIZER;

PropertyFactory::PropertyFactory(Controller * _controller, const char * _configDir)
	: ActionListener("PropertyFactory"){
	AAL_DEBUG(logger, "Constructor");

	this->configDir.clear();
	this->configDir.append(_configDir);

	this->controller = _controller;
	this->controller->addListener(this);

	this->propertyXmlFile.clear();
	this->propertyXmlFile.append(_configDir);
	this->propertyXmlFile.append("/properties.xml");

	// check if an properties.xml file exists
	char * targetName = canonicalize_file_name(propertyXmlFile.c_str());
	if(!targetName){
		// file doesn't exists
		AAL_INFO(logger, "File: %s doesn't exists. Creating a new one", propertyXmlFile.c_str());
		serializePropertyFile();
	}
	delete(targetName);

	// build the definitions;
	this->properties.push_back(new Property(AALPROP_ARGBCOLOR_TEXT,Property::ARGBCOLOR, "FFFFFFFF"));
	this->properties.push_back(new Property(AALPROP_ARGBCOLOR_CONTENTPANE,Property::ARGBCOLOR, "FF000000"));
	this->properties.push_back(new Property(AALPROP_ARGBCOLOR_TEXTPANE,Property::ARGBCOLOR, "80000000"));
	this->properties.push_back(new Property(AALPROP_ARGBCOLOR_CONTROLPANE,Property::ARGBCOLOR, "80000000"));
	this->properties.push_back(new Property(AALPROP_ARGBCOLOR_SIGNALINFO,Property::ARGBCOLOR, "800000FF"));
	this->properties.push_back(new Property(AALPROP_ARGBCOLOR_SIGNALWARN,Property::ARGBCOLOR, "80FFA500"));
	this->properties.push_back(new Property(AALPROP_ARGBCOLOR_SIGNALALARM,Property::ARGBCOLOR, "80FF0000"));
	this->properties.push_back(new Property(AALPROP_DEFAULTFLAT,Property::INTEGER,0));
	this->properties.push_back(new Property(AALPROP_AALBACKEND_HOST,Property::STRING, "192.168.60.120"));
	this->properties.push_back(new Property(AALPROP_AALBACKEND_PORT,Property::INTEGER, 8080));
	this->properties.push_back(new Property(AALPROP_TTS_MARY_HOST,Property::STRING, "192.168.60.120"));
	this->properties.push_back(new Property(AALPROP_TTS_MARY_PORT,Property::INTEGER,59125));
	vector<string> audioTypes;
	audioTypes.push_back(AALPROP_AUDIO_TYPE_BEEP);
	audioTypes.push_back(AALPROP_AUDIO_TYPE_ESPEAK);
	audioTypes.push_back(AALPROP_AUDIO_TYPE_MARY);
	this->properties.push_back(new Property(AALPROP_AUDIO_TYPE,Property::ENUMARATION, AALPROP_AUDIO_TYPE_BEEP, &audioTypes));
	this->properties.push_back(new Property(AALPROP_AUDIO_VOLUME,Property::INTEGER, 80));
	this->properties.push_back(new Property(AALPROP_AALITEM_DEFAULT_WIDTH,Property::DOUBLE, 80.5));
	this->properties.push_back(new Property(AALPROP_AALITEM_DEFAULT_HEIGHT,Property::DOUBLE, 90.5));
	deSerializePropFile();
}

PropertyFactory::~PropertyFactory(){
	AAL_DEBUG(logger, "Destructor");
	serializePropertyFile();
	while(this->properties.size() > 0){
		Property * property = this->properties.back();
		AAL_TRACE(logger,"Call destructor for property %s id %d", property->name.c_str(),property->id);
		this->properties.pop_back();
		delete(property);
	}
}

Property * PropertyFactory::getProperty(const char * _name) throw(string){
	vector<Property*>::iterator it;
	for (it=this->properties.begin(); it!=this->properties.end(); it++ ){
		Property * propDef = (*it);
		if(propDef->name.compare(_name) == 0){
			return propDef;
		}
	}
	AAL_WARN(logger, "Property %s not found in the local list.", _name);
	throw string("Property not found");
}

void PropertyFactory::serializePropertyFile(){

	pthread_rwlock_rdlock(&propertyFactoryRWLock);

	int length = 100;
	char integerBuffer[length];
	XMLCh tempStr0[length];
	XMLCh tempStr1[length];

	XMLString::transcode("LS", tempStr0, length-1);
	DOMImplementation * impl = DOMImplementationRegistry::getDOMImplementation(tempStr0);
	XMLString::transcode("Properties", tempStr0, length-1);
	DOMDocument * doc = impl->createDocument(0, tempStr0, 0);

	vector<Property*>::iterator it;
	for (it=this->properties.begin(); it!=this->properties.end(); it++ ){
		Property * propDef = (*it);
		AAL_DEBUG(logger, "Serialising property %s id %d values %d, %4.2f, %s",propDef->name.c_str(),propDef->id, propDef->getIntValue(), propDef->doubleValue, propDef->stringValue.c_str());

		XMLString::transcode("Property", tempStr0, length-1);
		DOMElement * item = doc->createElement(tempStr0);

		XMLString::transcode("name", tempStr0, length-1);
		XMLString::transcode(propDef->name.c_str(), tempStr1, length-1);
		item->setAttribute(tempStr0,tempStr1);

		switch (propDef->type){
			case Property::INTEGER:{
				snprintf(integerBuffer, length-1, "%d", propDef->getIntValue());
				XMLString::transcode("value", tempStr0, length-1);
				XMLString::transcode(integerBuffer, tempStr1, length-1);
				item->setAttribute(tempStr0,tempStr1);
			}
			break;
			case Property::DOUBLE:{
				snprintf(integerBuffer, length-1, "%4.2f", propDef->doubleValue);
				XMLString::transcode("value", tempStr0, length-1);
				XMLString::transcode(integerBuffer, tempStr1, length-1);
				item->setAttribute(tempStr0,tempStr1);
			}
			break;
			default:{
				XMLString::transcode("value", tempStr0, length-1);
				XMLString::transcode(propDef->stringValue.c_str(), tempStr1, length-1);
				item->setAttribute(tempStr0,tempStr1);
			}
		}
		doc->getDocumentElement()->appendChild(item);
	}

	Tools::createXmlFileFromDOMDocument(this->propertyXmlFile.c_str(),doc);
	doc->release();
	AAL_DEBUG(logger, "File: %s created", this->propertyXmlFile.c_str());

	pthread_rwlock_unlock(&propertyFactoryRWLock);
}

void PropertyFactory::deSerializePropFile(){

	pthread_rwlock_rdlock(&propertyFactoryRWLock);
	AAL_DEBUG(logger, "File: %s", this->propertyXmlFile.c_str());
	XercesDOMParser parser;
	DOMDocument * doc = Tools::createDOMFromXmlFile(this->propertyXmlFile.c_str(), &parser);
	if(doc != NULL){
		DOMNodeList * childNodes = doc->getDocumentElement()->getChildNodes();
		for(XMLSize_t childIndex = 0; childIndex < childNodes->getLength(); ++childIndex ){
			DOMNode * childNode = childNodes->item(childIndex);

			char * value = XMLString::transcode(childNode->getNodeName());
			if(strcmp(value,"Property") != 0){
				continue;
			}
			XMLString::release(&value);

			DOMElement * child = (DOMElement*)childNodes->item(childIndex);
			string name = "";
			Tools::getStringAttributeValue(child, "name", &name);
			try {
				Property * propDef = getProperty(name.c_str());
				switch (propDef->type){
					case Property::INTEGER:{
						try{
							int value = Tools::getIntAttributeValue(child, "value");
							propDef->setIntValue(value);
						} catch(int){
							AAL_ERROR(logger, "Can't parse %s property value to a valid int value", name.c_str());
						}
					}
					break;
					case Property::DOUBLE:{
						try{
							double value = Tools::getDoubleAttributeValue(child, "value");
							propDef->doubleValue = value;
						} catch(int){
							AAL_ERROR(logger, "Can't parse %s property value to a valid double value", name.c_str());
						}
					}
					break;
					default:{
						Tools::getStringAttributeValue(child, "value",&propDef->stringValue);
					}
				}
			} catch (string) {
				AAL_ERROR(logger, "Can't deserialize property %s. See PropertyFactory.h for a list of used properties.", name.c_str())
			}
		}
	} else {
		AAL_WARN(logger, "Can't deserialize file: %s", this->propertyXmlFile.c_str());
	}
	pthread_rwlock_unlock(&propertyFactoryRWLock);
}

vector<Property*> PropertyFactory::getProperties(){
	return this->properties;
}

int PropertyFactory::getIntProperty(const char * _name, int _defaultValue){
	pthread_rwlock_rdlock(&propertyFactoryRWLock);
	try {
		Property * propDef = getProperty(_name);
		if(propDef->type != Property::INTEGER){
			AAL_WARN(logger, "Property %s isn't an INTEGER. It's a %s. Returning default value %d", _name, propDef->typeToString(), _defaultValue);
		}
		pthread_rwlock_unlock(&propertyFactoryRWLock);
		return propDef->getIntValue();
	} catch (...){
		AAL_WARN(logger, "Can't return property %s. See PropertyFactory.h for a list of used properties. Returning default value %d", _name, _defaultValue);
	}
	pthread_rwlock_unlock(&propertyFactoryRWLock);
	return _defaultValue;
}

double PropertyFactory::getDoubleProperty(const char * _name, double _defaultValue){
	pthread_rwlock_rdlock(&propertyFactoryRWLock);
	try {
		Property * propDef = getProperty(_name);
		if(propDef->type != Property::DOUBLE){
			AAL_WARN(logger, "Property %s isn't an DOUBLE. It's a %s. Returning default value %4.2f", _name, propDef->typeToString(), _defaultValue);
		}
		pthread_rwlock_unlock(&propertyFactoryRWLock);
		return propDef->doubleValue;
	} catch (...){
		AAL_WARN(logger, "Can't return property %s. See PropertyFactory.h for a list of used properties. Returning default value %4.2f", _name, _defaultValue);
	}
	pthread_rwlock_unlock(&propertyFactoryRWLock);
	return _defaultValue;
}

const char * PropertyFactory::getStringProperty(const char * _name, const char * _defaultValue){
	pthread_rwlock_rdlock(&propertyFactoryRWLock);
	try {
		Property * propDef = getProperty(_name);
		if(propDef->type == Property::INTEGER || propDef->type == Property::DOUBLE){
			AAL_WARN(logger, "Property %s isn't an STRING. It's a %s. Returning default value %s", _name, propDef->typeToString(), _defaultValue);
		}
		pthread_rwlock_unlock(&propertyFactoryRWLock);
		return propDef->stringValue.c_str();
	} catch (...){
		AAL_WARN(logger, "Can't return property %s. See PropertyFactory.h for a list of used properties. Returning default value %s", _name, _defaultValue);
	}
	pthread_rwlock_unlock(&propertyFactoryRWLock);
	return _defaultValue;
}

bool PropertyFactory::updateProperty(const char * _name, const char * _value, string * _response){
	pthread_rwlock_rdlock(&propertyFactoryRWLock);
	char resBuff[500];
	try {
		Property * propDef = getProperty(_name);
		AAL_DEBUG(logger, "Updating property %s %d to a value %s", _name, propDef->id, _value);
		switch (propDef->type){
			case Property::INTEGER:{
				try{
					int value = Tools::getIntFromString(_value);
					propDef->setIntValue(value);
				} catch(int){
					sprintf(resBuff,"Can't update property %s because it's of type INTEGER but given value %s can't be parsed to int. Aborting...", _name, _value);
					AAL_WARN(logger, resBuff);
					_response->clear();
					_response->append(resBuff);
					pthread_rwlock_unlock(&propertyFactoryRWLock);
					return false;
				}
			}
			AAL_DEBUG(logger, "Property %s id %d has now values %d, %4.2f, %s",propDef->name.c_str(),propDef->id, propDef->getIntValue(), propDef->doubleValue, propDef->stringValue.c_str());
			break;
			case Property::DOUBLE:{
				try{
					double value = Tools::getDoubleFromString(_value);
					propDef->doubleValue = value;
				} catch(int){
					sprintf(resBuff,"Can't update property %s because it's of type DOUBLE but given value %s can't be parsed to int. Aborting...", _name, _value);
					AAL_WARN(logger, resBuff);
					_response->clear();
					_response->append(resBuff);
					pthread_rwlock_unlock(&propertyFactoryRWLock);
					return false;
				}
			}
			AAL_DEBUG(logger, "Property %s id %d has now values %d, %4.2f, %s",propDef->name.c_str(),propDef->id, propDef->getIntValue(), propDef->doubleValue, propDef->stringValue.c_str());
			break;
			default:{
				propDef->stringValue = _value;
			}
			AAL_DEBUG(logger, "Property %s id %d has now values %d, %4.2f, %s",propDef->name.c_str(),propDef->id, propDef->getIntValue(), propDef->doubleValue, propDef->stringValue.c_str());
		}
	} catch (...){
		sprintf(resBuff,"Can't update property %s because not found. See PropertyFactory.h for a list of available properties. Aborting...", _name);
		AAL_WARN(logger, resBuff);
		_response->clear();
		_response->append(resBuff);
		pthread_rwlock_unlock(&propertyFactoryRWLock);
		return false;
	}
	serializePropertyFile();
	sprintf(resBuff,"Property %s successfully updated.", _name);
	AAL_INFO(logger, resBuff);
	_response->append(resBuff);
	pthread_rwlock_unlock(&propertyFactoryRWLock);
	return true;
}


void PropertyFactory::doAction(AALAction * _action){
	// I don't have to lisen for any actions...
	// Maybe in the future
}

void PropertyFactory::doMainSavedAction(){
	// don't need to listen to the main thread loop
}
